package link.correction;

import java.io.IOException;
import java.io.InputStream;

public class Hamming8_4Corrector extends InputStream implements StreamCorrector {

	InputStream in;
	
	public Hamming8_4Corrector(InputStream bytes){
		in = bytes;
	}
	
	@Override
	/**
	 * This method blocks until bytes are available
	 */
	public int read() throws IOException, NotCorrectableException {
		if(available() < 1) throw new IOException("No bytes to read");
		
		byte[] buf = new byte[2];
		int i = in.read(buf, 0, 2);
		if(i < 2)
			throw new IOException("End of stream reached"); //available() should guarantee that i == 2 since two bytes need to be in.available 
															//for available() to return >0		
		
		return decode(buf);
	}

	@Override
	public int available() throws IOException{
		return in.available() >> 1; //0 if in.available() < 2 | in.available() / 2 otherwise
	}

	@Override
	public void finish() {
		//Hamming (8,4) is byte aligned so we don't need this
		//there should not ba a list single nibble
	}
	
	/**
	 * returns the byte represented by these two (8,4) Hamming encoded bytes
	 * bs[0] contains the byte encoding the rightmost 4 bits
	 * bs[1] contains the byte encoding the leftmost 4 bits
	 * @param bs the bytes to decode/correct, bs.length should be 2
	 * @return the byte that is represented by these two bytes
	 * @require bs.length == 2
	 */
	private static byte decode(byte[] bs) throws NotCorrectableException{
		byte res = decodeNibble(bs[0]);
		res |= (decodeNibble(bs[1]) << 4);
		return res;
	}
	
	/**
	 * returns the nibble represented by this (8,4) Hamming encoded byte
	 * @param b the (8,4) Hamming encoded byte
	 * @return the nibble that is represented by this byte, the significant bits are in the 
	 * 			rightmost four bits
	 */
	private static byte decodeNibble(byte b) throws NotCorrectableException{
		//b = p0 p1 d0 p2 d1 d2 d3 p3
		//    7  6  5  4  3  2  1  0
		int errorPos = 0; //the position that the biterror is in, note first (leftmost) position is 1
		//start checking the parity bits, the actual p0 + the bits checked by p0 should be 0
		//p0 = (p0 +) d0 + d1 + d3 
		if((((b >> 7) & 0x01) + ((b >> 5) & 0x01) + ((b >> 3) & 0x01) + ((b >> 1) & 0x01) & 0x01) != 0x00)
			errorPos += 1;
		//p1 = (p1 +) d0 + d2 + d3
		if((((b >> 6) & 0x01) + ((b >> 5) & 0x01) + ((b >> 2) & 0x01) + ((b >> 1) & 0x01) & 0x01) != 0x00)
			errorPos += 2;
		//p2 = (p2 +) d1 + d2 + d3
		if((((b >> 4) & 0x01) + ((b >> 3) & 0x01) + ((b >> 2) & 0x01) + ((b >> 1) & 0x01) & 0x01) != 0x00)
			errorPos += 4;
		//if p3 is correct, but there are errors (errorPos > 0) then it means there are 2 bit errors and we can't Correct
		//when only p3 is incorrect it usually means that this is the flipped bit
		boolean p3 = (((b >> 7) & 0x01) + ((b >> 6) & 0x01) + ((b >> 5) & 0x01) + ((b >> 4) & 0x01) + 
				((b >> 3) & 0x01) + ((b >> 2) & 0x01) + ((b >> 1) & 0x01 + ((b >> 0) & 0x01)) & 0x01) == 0x00;
		if(p3 && errorPos > 0)
			throw new NotCorrectableException("Multiple bit errors");
		
		//Now we correct a bit error if there is one, we also correct P-bits, even though we don't actually care
		//about their value at this point.
		if(errorPos > 0){
			b ^= (0x0080 >> (errorPos - 1));
		}
		
		//and now we assemble the nibble of data
		//this should look like: res = - - - - d0 d1 d2 d3
		byte res = 0x00;
		res |= ((byte) (b >> 1)) & 0x07; //add in d3 and d2 and d1
		res |= ((byte) (b >> 2)) & 0x08; //add in d0		
		
		return res;
	}
	
	/**
	 * Corrects the provided byte[] if it is (8,4) hamming encoded 
	 * by Hamming8_4Addres. This means that for every pair i and i+1 of bytes
	 * the leftmost nibble is represented by byte i and the rightmost nibble
	 * by byte i + 1
	 * @param m the array of bytes to correct
	 * @return an array of correctted bytes
	 * @throws NotCorrectableException if any of the bytes in the stream can not be corrected
	 * @requires 0<m.length
	 * @requires m.length%2 = 0
	 * @ensures \result.length = m.length/2
	 */
	public static byte[] correct(byte[] m) throws NotCorrectableException{
		byte[] res = new byte[m.length/2];
		int j = 0;
		for(int i=0; i<m.length; i+=2){
			byte[] x = new byte[2];
			x[0] = m[i];
			x[1] = m[i+1];
			try{
				res[j] = decode(x);
			} catch(NotCorrectableException e){
				throw new NotCorrectableException("Could not correct byte at position "+i+" of input");
			}
		}
		return res;
	}

}


















